/*
 * IDispatched.h
 *
 * This source file is part of the FoundationDB open source project
 *
 * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef _IDISPATCHED_H_
#define _IDISPATCHED_H_

#pragma once

#include "DocumentError.h"
#include "flow/flow.h"

#include <map>

template <class T, typename K, typename F>
struct IDispatched {
	static std::map<K, F>& dispatches() {
		static std::map<K, F> theDispatches;
		return theDispatches;
	}
	static F const& dispatch(K k) {
		auto it = dispatches().find(k);
		if (it == dispatches().end())
			throw bad_dispatch();
		return it->second;
	}
};

#define REGISTER_DISPATCHED(Type, Instance, Key, Func)                                                                 \
	struct Type##Instance {                                                                                            \
		Type##Instance() { Type::dispatches()[Key] = Func; }                                                           \
	};                                                                                                                 \
	Type##Instance _Type##Instance

#define REGISTER_COMMAND(Type, Instance, Key, Func) REGISTER_DISPATCHED(Type, Instance, Instance::Key, Instance::Func)

/**
 * REGISTER_COMMAND is used for dispatching from type to static
 * function. For example, to call one of multiple functions of
 * (int, int) -> int, based on a string, you would write:
 *
 *   struct BinaryArithmeticOp : IDispatched<BinaryArithmeticOp, std::string, std::function< int(int, int) >> {
 *       static int call( std::string const& op, int a, int b ) {
 *           return dispatch( op )( a, b );
 *       }
 *   };
 *
 *   struct AddOp {
 *       static constexpr const char* opname = "+";
 *       static int call( int a, in b ) {
 *           return a + b;
 *       }
 *   };
 *
 * AddOp needs to be registered with REGISTER_COMMAND:
 *
 *   REGISTER_COMMAND( BinaryArithmeticOp, AddOp, opname, call );
 *
 * If each BinaryArithmeticOp will have the same fields opname and
 * call, it is probably easier to define a convenience macro:
 *
 *   #define REGISTER_BINARY_OP(Op) REGISTER_COMMAND(BinaryArithmeticOp, Op, opname, call)
 *
 * The registration would then become:
 *
 *   REGISTER_BINARY_OP( AddOp );
 *
 * Given std::string op, int x and int y, you would dynamically call
 * the correct op with:
 *
 *   BinaryArithmeticOp::call( op, x, y );
 */

#define REGISTER_FACTORY(Type, Instance, Key)                                                                          \
	REGISTER_DISPATCHED(Type, Instance, Instance::Key, Type::Factory<Instance>::create)

/**
 * REGISTER_FACTORY is a formalized convention to simplify creating new
 * or singleton objects that satisfy an interface, dispatched by a
 * value of a given type. The type must have a nested, templated struct
 * named Factory, which must have a method create that returns the
 * desired type.
 *
 * For example:
 *
 *   struct Message : IDispatched<Message, std::string, std::function< Message*(const char*) >>,
 * ReferenceCounted<Message> { static Reference<Message> create( std::string const& message_type, const char* name ) {
 *           return Reference<Message>( dispatch( message_type )( name ) );
 *       }
 *
 *       virtual std::string toString() = 0;
 *
 *       template <class MessageType>
 *       struct Factory {
 *           static Message* create( const char* name ) {
 *               return (Message*)( new MessageType( name ) );
 *           }
 *       };
 *   };
 *
 *   struct ExclaimMessage : Message {
 *       static constexpr const char* msgname = "exclaim";
 *       ExclaimMessage( const char* name ) : name(name) {}
 *       virtual std::string toString() {
 *           std::string s = format( "Hello, %s!", name );
 *       }
 *   };
 *
 * Each message must also be registered:
 *
 *   REGISTER_FACTORY( Message, ExclaimMessage, msgname );
 *
 * This may also be shorted for a given type with another macro:
 *
 *   #define REGISTER_MESSAGE(Msg) REGISTER_FACTOR(Message, Msg, msgname)
 *
 * And then be called as:
 *
 *   REGISTER_MESSAGE( ExclaimMessage );
 *
 * Given std::string msgtype and const char* name, you would construct
 * a new message with:
 *
 *   Reference<Message> msg = Message::create( msgtype, name );
 */

#endif /* _IDISPATCHED_H_ */
